/*******************************************************************************
 * Copyright (c) 2010, 2018 西安秦晔信息科技有限公司
 * Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 *******************************************************************************/
package com.qinyeit.webapp.security.filter;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.qinyeit.serviceapp.security.token.RestaurantChainAuthenticationToken;
import com.qinyeit.webapp.security.token.EmployeeAuthenticationToken;
import com.qinyetech.springstage.core.security.StatelessSecurityToken;
import com.qinyetech.springstage.core.security.TerminalDevice;
import com.qinyetech.springstage.core.security.exception.JcaptchaCodeException;
import com.qinyetech.springstage.core.web.jcaptcha.JCaptcha;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.StringUtils;
import org.apache.shiro.web.filter.authc.FormAuthenticationFilter;
import org.apache.shiro.web.util.SavedRequest;
import org.apache.shiro.web.util.WebUtils;
import org.springframework.http.MediaType;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

import static com.qinyetech.springstage.core.security.SecurityHttpHeader.X_ACCESS_DEVICE;
import static com.qinyetech.springstage.core.security.SecurityHttpHeader.X_ACCESS_SESSION_ID;

/**
 * <p>ClassName: com.qinyeit.shirostarterdemo.security.filter.StatelessAuthenticationFilter
 * <p>Function: 无状态认证过滤器，包含登录，与登录认证，会话保持
 * <p>date: 2018-08-10 16:36
 *
 * @author wuqing
 * @version 1.0
 * @since JDK 1.8
 */
@Slf4j
public class StatelessAuthenticationFilter extends FormAuthenticationFilter {
    /**
     * 默认的验证码输入框标识
     */
    public static final String DEFAULT_CAPTCHA_PARAM = "captcha";

    public static final Long MOBILE_SESSION_TIMEOUT = 30 * 24 * 3600 * 1000L;//移动端session超时,单位毫秒，默认30天

    /**
     * 验证码输入框标识
     */
    private String captchaParam = DEFAULT_CAPTCHA_PARAM;

    /**
     * 是否打开图片验证码，默认不打开
     */
    private boolean jcaptchaEbabled = false;


    public void setJcaptchaEbabled(boolean jcaptchaEbabled) {
        this.jcaptchaEbabled = jcaptchaEbabled;
    }

    /**
     * 设置验证码输入框标识
     *
     * @return 验证码输入框标识
     */
    public void setCaptchaParam(String captchaParam) {
        this.captchaParam = captchaParam;
    }

    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
        if (isLoginRequest(request, response)) {
            if (isLoginSubmission(request, response)) //如果是登录提交，则注销原来的登录
            {
                Subject subject = this.getSubject(request, response);
                if (subject != null && subject.isAuthenticated()) {
                    subject.logout();
                }
            }
        }
        return super.isAccessAllowed(request, response, mappedValue);
    }

    protected String getCaptcha(ServletRequest request) {
        return WebUtils.getCleanParam(request, captchaParam);
    }

    @Override
    protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) {
        String username = getUsername(request);

        String password = getPassword(request);
        boolean rememberMe = isRememberMe(request);
        String host = getHost(request);
        String adminValue=WebUtils.toHttp(request).getParameter(EmployeeAuthenticationToken.ADMIN_PARAM_NAME);
        RestaurantChainAuthenticationToken token = new RestaurantChainAuthenticationToken(username, password, rememberMe,
                host, getTerminalDevice(request),adminValue);
        return token;
    }

    @Override
    protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception {
        //如果打开图片验证码校验，则校验，否则不校验
        if (jcaptchaEbabled) {
            String captcha = getCaptcha(request);
            if(!JCaptcha.validateResponse(WebUtils.toHttp(request),captcha)){
                //验证码错误
                return onLoginFailure(null,new JcaptchaCodeException(),request,response);
            }
        }
        return super.executeLogin(request, response);
    }

    /**
     * 获取发起请求的设备类型
     *
     * @return
     */
    protected TerminalDevice getTerminalDevice(ServletRequest request) {
        String device = WebUtils.toHttp(request).getHeader(X_ACCESS_DEVICE);
        if (!StringUtils.hasLength(device)) {
            return TerminalDevice.UNKNOWN;
        }
        try {
            return TerminalDevice.valueOf(device);
        } catch (Exception ex) {
            log.warn("unknown device:", ex);
            return TerminalDevice.UNKNOWN;
        }

    }


    @Override
    public boolean onLoginSuccess(AuthenticationToken token,
                                  Subject subject, ServletRequest request, ServletResponse response) {
        StatelessSecurityToken statelessSecurityToken = (StatelessSecurityToken) token;
        //移动端
        if (TerminalDevice.PCWEB != statelessSecurityToken.getTerminalDevice() && TerminalDevice.UNKNOWN != statelessSecurityToken.getTerminalDevice()) {
            //移动端默认保持30天
            subject.getSession().setTimeout(MOBILE_SESSION_TIMEOUT);
        }
        //响应头加入X-Access-Token(sessionID,用于保持会话)，
        // 请求头中必须携带X-Access-Token 才能保持会话
        WebUtils.toHttp(response).addHeader(X_ACCESS_SESSION_ID, subject.getSession().getId().toString());
        return true; //通过Controller
    }


    @Override
    protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException e,
                                     ServletRequest request, ServletResponse response) {
        setFailureAttribute(request, e);
        //login failed, let request continue back to the login page:
        return true;
    }

    /**
     * 访问被禁止处理，原来是跳转到登陆页面，如果是ajax访问，则需要返回json信息
     *
     * @param request
     * @param response
     * @return
     * @throws Exception
     */
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
        if (isLoginRequest(request, response)) {
            if (isLoginSubmission(request, response)) {
                if (log.isTraceEnabled()) {
                    log.trace("Login submission detected.  Attempting to execute login.");
                }
                return executeLogin(request, response);
            } else {
                if (log.isTraceEnabled()) {
                    log.trace("Login page view.");
                }
                //allow them to see the login page ;)
                return true;
            }
        } else {
            if (log.isTraceEnabled()) {
                log.trace("Attempting to access a path which requires authentication.  Forwarding to the " +
                        "Authentication url [" + getLoginUrl() + "]");
            }

            sendAuthenticationInfo(WebUtils.toHttp(request), WebUtils.toHttp(response));

            return false;
        }
    }

    private ObjectMapper mapper = new ObjectMapper();

    /**
     * ajax请求返回未认证信息
     */
    private void sendAuthenticationInfo(HttpServletRequest request, HttpServletResponse response) {
        response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
        response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
        SavedRequest savedRequest = new SavedRequest(request);
        Map<String, Object> errorResponse = new HashMap<>();
        errorResponse.put("success", false);
        errorResponse.put("message", "请登录");
        errorResponse.put("content", savedRequest);
        errorResponse.put("level", "error");
        errorResponse.put("status", HttpServletResponse.SC_UNAUTHORIZED);
        try {
            response.setCharacterEncoding("UTF-8");

            mapper.writer().writeValue(response.getWriter(), errorResponse);
        } catch (IOException e) {

        }
    }



}
